Skip to content

feat: container info sidebar and add sidebar updates#2830

Open
navinkarkera wants to merge 57 commits intoopenedx:masterfrom
open-craft:navin/fal-4293/container-info-sidebar
Open

feat: container info sidebar and add sidebar updates#2830
navinkarkera wants to merge 57 commits intoopenedx:masterfrom
open-craft:navin/fal-4293/container-info-sidebar

Conversation

@navinkarkera
Copy link
Contributor

@navinkarkera navinkarkera commented Jan 20, 2026

Description

screencast.mp4
  • Adds section, subsection and unit sidebar info tab in course outline as described in Course Outline: Section/Subsections/Unit Info Sidebar #2638
  • Updates the sidebar design and behaviour as per Course outline: Add sidebar updates #2826
  • Updates course outline to use react query and removes redux store usage as much as possible. Updated parts that require absolutely cannot work without redux without heavy refactoring (will require quiet some time) to work in tandem with react-query.
  • Due to above change, this PR has a lot of refactor to make sure data is properly synced in outline after any action.

Following items are not implemented:

Apologies for the huge number of changes, could be help it due to migrating parts to react-query

Supporting information

Testing instructions

Other information

Include anything else that will help reviewers and consumers understand the change.

  • Does this change depend on other changes elsewhere?
  • Any special concerns or limitations? For example: deprecations, migrations, security, or accessibility.

Best Practices Checklist

We're trying to move away from some deprecated patterns in this codebase. Please
check if your PR meets these recommendations before asking for a review:

  • Any new files are using TypeScript (.ts, .tsx).
  • Avoid propTypes and defaultProps in any new or modified code.
  • Tests should use the helpers in src/testUtils.tsx (specifically initializeMocks)
  • Do not add new fields to the Redux state/store. Use React Context to share state among multiple components.
  • Use React Query to load data from REST APIs. See any apiHooks.ts in this repo for examples.
  • All new i18n messages in messages.ts files have a description for translators to use.
  • Avoid using ../ in import paths. To import from parent folders, use @src, e.g. import { initializeMocks } from '@src/testUtils'; instead of from '../../../../testUtils'

@openedx-webhooks openedx-webhooks added open-source-contribution PR author is not from Axim or 2U core contributor PR author is a Core Contributor (who may or may not have write access to this repo). labels Jan 20, 2026
@openedx-webhooks
Copy link

openedx-webhooks commented Jan 20, 2026

Thanks for the pull request, @navinkarkera!

This repository is currently maintained by @bradenmacdonald.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@github-project-automation github-project-automation bot moved this to Needs Triage in Contributions Jan 20, 2026
@navinkarkera navinkarkera force-pushed the navin/fal-4293/container-info-sidebar branch from 5f8ac0b to feba274 Compare January 20, 2026 14:53
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Waiting on Author in Contributions Jan 20, 2026
@navinkarkera navinkarkera force-pushed the navin/fal-4293/container-info-sidebar branch 2 times, most recently from 9f4a330 to 4f29a90 Compare January 27, 2026 07:26
@codecov
Copy link

codecov bot commented Jan 27, 2026

Codecov Report

❌ Patch coverage is 95.44025% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.21%. Comparing base (7173b71) to head (4c07979).

Files with missing lines Patch % Lines
src/generic/resizable/Resizable.tsx 63.33% 11 Missing ⚠️
...e-outline/outline-sidebar/LibraryReferenceCard.tsx 94.59% 4 Missing ⚠️
...-outline/outline-sidebar/OutlineSidebarContext.tsx 94.11% 3 Missing ⚠️
src/course-outline/card-header/CardHeader.tsx 90.47% 2 Missing ⚠️
src/course-outline/hooks.jsx 94.28% 2 Missing ⚠️
...tline/outline-sidebar/info-sidebar/InfoSidebar.tsx 91.30% 2 Missing ⚠️
...e/outline-sidebar/info-sidebar/UnitInfoSidebar.tsx 94.59% 2 Missing ⚠️
src/course-outline/data/apiHooks.ts 98.00% 1 Missing ⚠️
src/course-outline/outline-sidebar/AddSidebar.tsx 98.63% 1 Missing ⚠️
...se-outline/outline-sidebar/OutlineAlignSidebar.tsx 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2830      +/-   ##
==========================================
- Coverage   95.21%   95.21%   -0.01%     
==========================================
  Files        1318     1326       +8     
  Lines       30018    30338     +320     
  Branches     6543     6863     +320     
==========================================
+ Hits        28582    28886     +304     
- Misses       1379     1383       +4     
- Partials       57       69      +12     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@navinkarkera navinkarkera force-pushed the navin/fal-4293/container-info-sidebar branch 4 times, most recently from 3ceae1c to 5590401 Compare February 1, 2026 11:14
@navinkarkera navinkarkera marked this pull request as ready for review February 2, 2026 15:14
@navinkarkera navinkarkera requested a review from rpenido February 2, 2026 15:18
@navinkarkera navinkarkera force-pushed the navin/fal-4293/container-info-sidebar branch from 2cc17aa to e685ab3 Compare February 3, 2026 07:11
Copy link
Contributor

@rpenido rpenido left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, awesome work here @navinkarkera!
Thanks for all the refactors! After this PR, we will be way closer to saying goodbye to our redux store.

I asked a few clarifying questions about our invalidation strategy.
Everything worked as expected, except for a small bug with the taxonomy button on the header.

iconAs={EditIcon}
onClick={onClickEdit}
onClick={onEditClick}
// @ts-ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was fixed upstream

Suggested change
// @ts-ignore

Comment on lines 205 to 206
* @param {string} itemId
* @returns {Promise<Object>}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this anymore.

Suggested change
* @param {string} itemId
* @returns {Promise<Object>}

await callback?.(data.locator, variables.parentLocator);
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseItemId(variables.parentLocator) });
queryClient.invalidateQueries({
queryKey: courseOutlineQueryKeys.courseDetails(getCourseKey(data.locator)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to invalidate the courseDetails here? Do you know which data it sync?

Maybe this is fixing a sync issue by its side effects: refetching the course details would cause a re-render, which will resync the data.

It would be preferable that we find the root cause, or, if it is starting to become complicated, just invalidate the whole course with courseOutlineQueryKeys.course(getCourseKey(data.locator)),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rpenido We invalidate courseDetails to update the course sync status which is displayed in status bar in course outline. I am not sure what you mean by re-render but this api is quiet small and only used in fetching course metadata (not the whole course outline).

Comment on lines 78 to 79
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseDetails(courseId) });
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseItemId(variables.itemId) });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to invalidate anything besides containerComparisonQueryKeys.course(courseId) here, since they all have the same predicate.

Could you check it?

Suggested change
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseDetails(courseId) });
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseItemId(variables.itemId) });

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, courseOutlineQueryKeys.courseDetails is different from courseOutlineQueryKeys.course and courseOutlineQueryKeys.courseItem. They are related to fetching outline data while course details will just fetch course metadata.

If we invalidate courseOutlineQueryKeys.course(courseId), it will invalidate all course item queries unnecessarily. We only need to invalidate the item itself and the course metadata to update status bar.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad! I just noted that containerComparisonQueryKeys.course was a different key.

mutationFn: publishCourseItem,
onSettled: async (_data, _err, itemId) => {
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseItemId(itemId) });
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseDetails(getCourseKey(itemId)) });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing with the courseDetail here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/**
* Creates a resizable box that can be dragged to resize the width from the left side.
*/
export const ResizableBox = ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!!

Comment on lines +66 to +67
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */}
<div className="resizable-handle" onMouseDown={onMouseDown} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice to add a button/handler here. I just noticed that the sidebar was resizable after looking at the code.
Also, it should help with accessibility.

This could be discussed upstream and implemented in a follow-up task if needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. However does the resizing really matter in accessibility?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I have no idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For people who can see fine but only use a keyboard and can't use a mouse, it may be nice if there were a way to resize using the keyboard. I don't think that's a requirement here though, just something that would be a nice enhancement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bradenmacdonald Makes sense.

Comment on lines +17 to +22
queryClient.invalidateQueries({
queryKey: courseOutlineQueryKeys.courseItemId(contentId),
});
queryClient.invalidateQueries({
queryKey: courseOutlineQueryKeys.courseDetails(courseKey),
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as commented above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 111 to 113
queryClient.invalidateQueries({
queryKey: courseOutlineQueryKeys.courseItemId(currentSelection?.sectionId),
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we invalidating a section when opening a unit here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was used to invalidate parent data after a new unit is created but not needed anymore as it is handled in the source.

const showNewSidebar = getConfig().ENABLE_COURSE_OUTLINE_NEW_DESIGN?.toString().toLowerCase() === 'true';
if (showNewSidebar) {
setCurrentPageKey('align', cardId);
setCurrentPageKey('align');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clicking on the taxonomy tag on the card header is crashing here:

TypeError
can't access property "component", pages[currentPageKey] is undefined
Call Stack
 Sidebar
  src/generic/sidebar/Sidebar.tsx:76:28
 renderWithHooks
  node_modules/react-dom/cjs/react-dom.development.js:15486:27
 updateFunctionComponent
  node_modules/react-dom/cjs/react-dom.development.js:19613:20
 beginWork
  node_modules/react-dom/cjs/react-dom.development.js:21636:16
 callCallback
  node_modules/react-dom/cjs/react-dom.development.js:4164:14
 invokeGuardedCallbackDev
  node_modules/react-dom/cjs/react-dom.development.js:4213:16
 invokeGuardedCallback
  node_modules/react-dom/cjs/react-dom.development.js:4277:31
 beginWork$1
  node_modules/react-dom/cjs/react-dom.development.js:27486:28
 performUnitOfWork
  node_modules/react-dom/cjs/react-dom.development.js:26592:12
 workLoopSync
  node_modules/react-dom/cjs/react-dom.development.js:26501:22

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rpenido This was a weird one. My guess is that getConfig doesn't have all values set at the top level (initial render), so it decides that taxonomy is not enabled and so align page is not included in sidebar.

I had to update sidebar page provider to set the pages value inside OutlineSidebarPagesProvider which is component and so getConfig returns correct values and the align page is included. See 112b256

But this change now forces using OutlineSidebarPagesProvider and also the user of this provider has to use pages props to update sidebar pages in plugin.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!
I don't think that we need this pages props here.
A plugin could override it by nesting another Provider, the way is shown in the comment before the context.
And that way, we can have more control at the plugin to also remove pages, change order, etc..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch @navinkarkera!
I would prefer that we do not use the pages prop, so we could have more flexibility (removing pages, changing order, etc..) on the plugin end.

I ported your fix (removing the props) here: 10b24e8

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Updated here: ee9fa58

@navinkarkera navinkarkera requested a review from rpenido February 4, 2026 10:14
@navinkarkera navinkarkera force-pushed the navin/fal-4293/container-info-sidebar branch from 93f0d3a to 102034e Compare February 4, 2026 12:27
Copy link
Contributor

@rpenido rpenido left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍
Thank you for your work, @navinkarkera!

  • I tested this using the instructions from the PR
  • I read through the code
  • I checked for accessibility issues
  • Includes documentation

Just left one comment about removing the pages props from the provider.

@bradenmacdonald
Copy link
Contributor

Updates course outline to use react query and removes redux store usage as much as possible. Updated parts that require absolutely cannot work without redux without heavy refactoring (will require quiet some time) to work in tandem with react-query.

Yay, that's awesome! Is there any way to separate that into a separate PR or is it too tied in to the sidebar changes?

I'm tracking the redux removal in #2540 - could you please let me know which parts of that list I can update / link to this PR ?

{icon && <Icon src={icon} className="mr-1 text-primary" size="sm" />}
{title && (
<h3 className="h5 font-weight-bold text-primary mb-0">
<h5 className="font-weight-bold text-primary mb-0 mt-1">
Copy link
Contributor

@bradenmacdonald bradenmacdonald Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, to improve accessibility and browsing the document via heading navigation, this should only be an <h5> if there is also an <h4>, <h3>, <h2>, and <h1> already on the page. So it should probably stay as an <h3> or perhaps even an <h2> (if there is no <h2> above), but it can be styled using the h5 class to look like an h5.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, the sidebar has an h2 (title of sidebar) so making this h3 seems like the correct option.

Copy link
Contributor

@ChrisChV ChrisChV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@navinkarkera It's an incredible job! Thanks for the refactoring! It looks good; I've left some comments. I want to test the code, but I'll do that tomorrow.

Comment on lines 144 to 146
// istanbul ignore next
data.shouldScroll = true;
// istanbul ignore next
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@navinkarkera Is it necessary to have two istanbul ignore? I think it would be best to ignore the whole block

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

});
},
// istanbul ignore next
addUnit: (state: CourseOutlineState, { payload }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@navinkarkera Is it absolutely necessary to add this? If it's too complex to remove, could you add a comment explaining how to remove it? We are actively working on removing React Redux.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now it is required, we cannot really remove it until we migrate the whole course outline to react-query. I'll add a comment here.

Comment on lines 258 to 263
queryClient.invalidateQueries({
queryKey: courseOutlineQueryKeys.courseItemId(currentSelection.subsectionId),
});
queryClient.invalidateQueries({
queryKey: courseOutlineQueryKeys.courseItemId(currentSelection.sectionId),
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@navinkarkera Thank you for all this refactoring! I know it's tiring work. We are currently migrating Redux to React Query in #2540. If you have extra CC hours, you can continue this work there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should create an info-sidebar folder and organize all the info-sidebar related components inside it. We should also add this component to that folder; it's a shared component, but it's used within the context of the info-sidebar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. 480e2fa

@navinkarkera navinkarkera force-pushed the navin/fal-4293/container-info-sidebar branch from ee9fa58 to 8a915c4 Compare February 6, 2026 07:34
@navinkarkera
Copy link
Contributor Author

navinkarkera commented Feb 6, 2026

Yay, that's awesome! Is there any way to separate that into a separate PR or is it too tied in to the sidebar changes?

@bradenmacdonald Unfortunately it is not easy to separate them. I had to refactor mostly due to sync and access issues inside sidebar without fetching data multiple times.

I'm tracking the redux removal in #2540 - could you please let me know which parts of that list I can update / link to this PR ?

This PR migrates some parts of courseOutline from redux to react-query but still a lot is left.

@ChrisChV
Copy link
Contributor

ChrisChV commented Feb 6, 2026

@navinkarkera I found an issue with the "Unlink from library" for units. It doesn't happen in master

https://www.loom.com/share/f276b05ccd75401f9eb202b9e68ab84a

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@navinkarkera There's only one component in this file, I think it would be better to rename it: sharedComponents.tsx > InfoSection.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core contributor PR author is a Core Contributor (who may or may not have write access to this repo). open-source-contribution PR author is not from Axim or 2U

Projects

Status: Waiting on Author

Development

Successfully merging this pull request may close these issues.

5 participants